# =================================================================
#
# -*- coding:gbk -*-
# @Date: 2020/2/16
# @Author: Tigerots
#
# =================================================================

# 导包
import re
import sys
import os.path
import binascii
import datetime
import time
import traceback

import serial
import serial.tools.list_ports

from PyQt5.QtCore import QThread, pyqtSignal


# if hasattr(sys, 'frozen'):# 一定要在import之前, 否则打包容易失败
#     os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH']


# 串口设置类 ====================================================
class SerialSetting:
    def __init__(self):

        self.ser = serial.Serial()  # baudrate=9600,port='COM1')
        # 串口设置
        self.ser.baudrate = 115200 # 波特率 115200
        self.ser.bytesize = serial.EIGHTBITS  # 数据位 8
        self.ser.parity = serial.PARITY_NONE  # 检验方式 N
        self.ser.stopbits = serial.STOPBITS_ONE  # 停止位 1
        self.ser.timeout = 0.1  # 设置读取超时
        self.ser.writetimeout = None  # 设置写入超时
        self.ser.interCharTimeout = None  # 设置当前字节间超时
        self.ser.xonxoff = False  # 软件数据流控制
        self.ser.rtscts = False  # 硬件数据流控制（需要接线），用于提升数据精度
        self.ser.dsrdtr = False  # 连接控制，用于建立连接
        self.ser.port = ''

    # 波特率改变
    def set_baudrate(self, bps):
        self.ser.baudrate = bps  # 波特率
        print(self.ser)  # 输出串口信息

    @staticmethod
    def list_ports():
        l_list = []
        p_list = list(serial.tools.list_ports.comports())
        if len(p_list) <= 0:
            print("没有发现端口!")
        else:
            for i in p_list:
                l_list.append(list(i)[0])
            print("可用端口名: ", l_list)
        return l_list

    # 正则匹配com号
    @staticmethod
    def rematch_com(ser_com_Text):
        com = re.match(r'^(COM)(\d{0,99999})', ser_com_Text)
        com1: str = com.group(0)
        return com1

    # 打开串口
    def port_open(self, ser_com_Text, ser_com_bps=115200):
        self.ser.baudrate = ser_com_bps  # 波特率
        self.ser.port = self.rematch_com(ser_com_Text)
        try:
            self.ser.open()

        except serial.SerialException:
            if self.ser.isOpen(): # 串口已经打开或被占用
                print("串口已经打开或被占用！详情如下: ")
                print(self.ser)  # 输出串口信息

            else:  # 弹窗显示串口重复打开
                print("串口打开失败！")

            return self.ser

        else:  # 正常串口打开
            print('串口 {} 打开成功, 详情如下: '.format(self.ser.port))
            print(self.ser)  # 输出串口信息
            return self.ser

# 串口发送线程 ====================================================
class SerialSendThread(QThread):
    ser_send_data_output = pyqtSignal(str, bytes)  # 数据输出信号

    def __init__(self):
        super(SerialSendThread, self).__init__()

        self.ser1 = serial.SerialBase()
        self.send_text1 = bytes() # 发送内容
        self.is_ascii_hex_bin = 0 # 是否十六进制发送
        self.next_row_p = 0 # 发送新行
        self.coding = 'GBK' # 默认编码格式
        self.pack_size = 1024 # 发送文件单包数据大小
        self.delay_ms = 10 # 两包之间的发送间隔
        self.is_file = False # 是否发送文件
        self.file_type = "txt"  # 文件类型(是否是二进制文件)
        self.send_file = "./"  # 如果发送文件类型, 问文件地址
        self.send_text = ""

    def set_pack_size(self, size=1024): # 发送文件单包数据大小
        self.pack_size = size

    def set_delay_ms(self, ms): # 两包之间的发送间隔
        self.delay_ms = ms

    def set_send_data(self, ser1, text_input):  # 处理run函数不能处理的东西
        self.is_file = False
        self.send_text = text_input
        self.ser1 = ser1

    def set_send_file(self, ser1, file_path, file_type="txt"):  # 处理run函数不能处理的东西
        self.is_file = True
        self.file_type = file_type # 文件类型(是否是二进制文件)
        self.send_file = file_path # 如果发送文件类型, 问文件地址
        self.ser1 = ser1

    def set_send_encode(self, code="gbk"): # 发送编码格式,gbk,gb2312,utf-8
        self.coding = code

    def set_hex_send(self, hsb=0): # 是否是十六进制发送
        # 0: 字符串发送
        # 1: 十六进制发送(输入字符串表示的十六进制)
        # 2: 二进制发送(输入为bin文件, 0x3A, 0x5B)
        self.is_ascii_hex_bin = hsb

    def next_row_open(self, next_row_p): # 发送新行
        self.next_row_p = next_row_p

    # 重写的线程运行函数(pyqt线程, segmentfault推荐使用)
    def run(self):
        if self.ser1.isOpen():
            if self.is_file:
                if self.file_type == "hex": # "hex"文件
                    try:
                        with open(file=self.send_file, mode='r') as file:
                            ser_send_cnt = 0
                            self.set_hex_send(1)
                            start = time.perf_counter()
                            self.file_size = os.path.getsize(self.send_file) # 获取文件大小
                            for line in file:
                                self.send_text = line
                                self.send_to_serial() # 发送数据
                                ser_send_cnt += len(line)
                                time.sleep(self.delay_ms / 1000)  # 延时 s
                                # self.progressBar.setValue((ser_send_cnt / self.file_size) * 100)

                            end = time.perf_counter() - start
                            msg = "\r=== 发送完成, 共%d字节, 用时%d秒 ===\r" % (self.file_size, end)
                            # self.signal_write_msg.emit(msg)  # 发送日志消息
                            print(msg)

                    except Exception as e:
                        print(e)

                elif self.file_type == "bin": # "bin"文件
                    try:
                        with open(file=self.send_file, mode='rb') as file:
                            ser_send_cnt = 0
                            self.set_hex_send(2) # bin文件发送
                            start = time.perf_counter()
                            self.file_size = os.path.getsize(self.send_file)  # 获取文件大小
                            for i in range(0, self.file_size, self.pack_size):
                                line = file.read(self.pack_size)
                                self.send_text = line
                                self.send_to_serial() # 发送数据
                                ser_send_cnt += len(line)
                                time.sleep(self.delay_ms / 1000)  # 延时 s
                                # self.progressBar.setValue((ser_send_cnt / self.file_size) * 100)

                            end = time.perf_counter() - start
                            msg = "\r\r=== 发送完成, 共%d字节, 用时%d秒 ===\r" % (self.file_size, end)
                            # self.signal_write_msg.emit(msg)  # 发送日志消息
                            print(msg)
                    except Exception as e:
                        print(e)

                else: # "txt" 文件
                    try:
                        with open(file=self.send_file, mode='r', encoding=self.coding) as file:
                            ser_send_cnt = 0
                            self.set_hex_send(0)
                            start: float = time.perf_counter()
                            self.file_size = os.path.getsize(self.send_file)  # 获取文件大小
                            for line in file:
                                self.send_text = line
                                self.send_to_serial()  # 发送数据
                                ser_send_cnt += len(line)
                                time.sleep(self.delay_ms / 1000)  # 延时 s
                                # self.progressBar.setValue((ser_send_cnt / self.file_size) * 100)

                            end = time.perf_counter() - start
                            msg = "\r=== 发送完成, 共%d字节, 用时%d秒 ===\r" % (self.file_size, end)
                            # self.signal_write_msg.emit(msg)  # 发送日志消息
                            print(msg)
                    except Exception as e:
                        print(e)
            else:
                self.send_to_serial()
                pass
        else:
            print("发送失败, 请先打开串口...")

    def send_to_serial(self):
        # 1. 十六进制发送(输入为字符串形势 "3A5B7C")
        if self.is_ascii_hex_bin == 1:
            self.send_text = self.send_text.replace('\n', '')  # 清除回车一定要放在最前
            self.send_text = self.send_text.replace(' ', '')  # 其次清除空白
            self.send_text_length = int(len(self.send_text))  # 最后才统计字数才不会出错
            print('输入区字符数' + str(self.send_text_length))

            # 判断字符数，如果是基数就截取最后一位数据出来不发
            if self.send_text_length % 2 == 0:
                self.send_text = self.send_text[0:self.send_text_length]
            else:
                self.send_text = self.send_text[0:self.send_text_length - 1]

            # 转换成ascii编码的16进制的byte类型,消除所有空格
            self.send_text1 = binascii.a2b_hex(self.send_text)

            # 显示部分
            second_send_data = binascii.b2a_hex(self.send_text1).decode()  # 乱序解码，借用编译好的acii码16进制重新输出（消除不规则空格和合并字符）
            pattern = re.compile('.{1,2}')  # 匹配出两个字符（数据类型为列表型）
            second_send_data = (' '.join(pattern.findall(second_send_data)))  # 匹配出两个字符后在后面加空格（顺序以排好）
            self.ser1.write(self.send_text1)
            qtshow_send_data = ('[' + str(datetime.datetime.now().strftime(
                                '%H:%M:%S.%f')) + ']' + '发送数据: ' + second_send_data + " ")  # 加上显示时间和发标签
            print(qtshow_send_data)

        # 2: 二进制发送(输入为bin文件, 0x3A, 0x5B)
        elif self.is_ascii_hex_bin == 2:
            self.ser1.write(self.send_text)

        # 0: 字符发送(编码根据设置,默认GBK)
        else:
            if self.next_row_p == 1: # 发送新行选中, 自动发送回车换行
                self.send_text1 = bytes(self.send_text + '\r\n', encoding=self.coding)
            else:
                try:
                    self.send_text1 = bytes(self.send_text, encoding=self.coding)
                except Exception as e:
                    print('解码错误: ')
                    print(e)
            # 写入
            self.ser1.write(self.send_text1)
            qtshow_send_data = ('[' + str(datetime.datetime.now().strftime(
                                '%H:%M:%S.%f')) + ']' + '发→○' + self.send_text + "  ")  # 加上显示时间和发标签
            print(self.send_text1)
            self.ser1.flushOutput()
            self.ser_send_data_output.emit(qtshow_send_data, self.send_text1)  # 发送数据出去进行添加到显示框和信标定位

# 串口接收线程 ====================================================
class SerialRecvThread(QThread):
    ser_recv_data_output = pyqtSignal(bytes, str)  # 数据输出信号
    ser_recv_error_output = pyqtSignal()  # 数据输出信号

    def __init__(self):
        super(SerialRecvThread, self).__init__()
        self.set_hex_show(1) # 是否是十六进制显示
        # self.set_sleep_time(0)
        # timeout = None：永远等待 / 直到收到请求的字节数
        # timeout = 0：非阻塞模式，在任何情况下立即返回，返回零或更多，直到请求的字节数
        # timeout = x：在请求的字节数可用时立即返回，否则等到超时到期并返回之前收到的所有字节。

    def set_sleep_time(self, sleep_time):  # 传递睡眠时间
        self.ser1.timeout = sleep_time

    def set_hex_show(self, hex_button):
        self.hex_show = hex_button

    def set_app_init(self, ser1, port_switch=0, port_refresh_switch=0, combobox_switch=0):  # 传递一次串口数据
        self.ser1 = ser1
        self.port_switch = port_switch  # 开启开关
        self.port_refresh_switch = port_refresh_switch  # 刷新开关
        self.combobox_switch = combobox_switch  # 随意变换串口开关
        self.hex_show = 0 # 十六进制显示

    def run(self):
        while self.ser1.isOpen() :
            # noinspection PyBroadException
            try:
                time.sleep(0.01) # 一般延时函数与串口inWaiting同时使用
                self.n = self.ser1.inWaiting()
                if self.n > 0:
                    if self.n >=    2048:
                        receive_data_buff = self.ser1.readline() # 读取一行数据
                    else:
                        receive_data_buff = self.ser1.read(self.n) # 读取全部数据

                    if self.hex_show == 1: # 十六进制显示
                        hex_receive_data = binascii.b2a_hex(receive_data_buff).decode(
                                            'utf-8')  # 将acii码形式的byte类型转为16进制的str类型
                        pattern = re.compile('.{1,2}')  # 最多匹配出两个字符
                        hex_data = (' '.join(pattern.findall(hex_receive_data)))  # 匹配出两个字符后在后面加空格（顺序以排好）
                        receive_data = hex_data
                        # print(receive_data)
                    else:
                        # noinspection PyBroadException
                        try:
                            receive_data = receive_data_buff.decode('gbk')
                        except Exception:
                            # print('收到数据非字符格式, 按十六进制解析')
                            hex_receive_data = binascii.b2a_hex(receive_data_buff).decode(
                                                'utf-8')  # 将acii码形式的byte类型转为16进制的str类型
                            pattern = re.compile('.{1,2}')  # 最多匹配出两个字符
                            hex_data = (' '.join(pattern.findall(hex_receive_data)))  # 匹配出两个字符后在后面加空格（顺序以排好）
                            receive_data = hex_data

                    # 调用接收数据处理程序, 传递的值分别为原始数据, 解码后的数据
                    self.ser_recv_data_output.emit(receive_data_buff, receive_data)

            except Exception:
                traceback.print_exc()
                print("Unexpected error:", sys.exc_info()[0])
                print('串口详情: ', end="")  # 输出串口是否开启
                print(self.ser1)  # 输出串口信息
                self.ser_recv_error_output.emit()  # 调用串口错误处理事件


if __name__ == '__main__':
    ser = SerialSetting()
    ser.list_ports()
    ser.port_open("COM4, COM5")

    ser_receive = SerialRecvThread()
    ser_receive.set_app_init(ser.ser)
    ser_receive.start()

    ser_send = SerialSendThread()

    # ser_send.set_hex_send(0) # 字符发送
    # text = "upgrade -mb\r\n"
    # ser_send.set_send_data(ser.ser, text)  # 把数据转进去多线程里面处理
    # ser_send.start()

    # text_ptah = "./user_app/rtthread.bin"
    text_ptah = "./test.py"
    ser_send.set_delay_ms(10)
    # ser_send.set_send_encode("utf-8")
    ser_send.set_send_file(ser.ser, text_ptah, "py")  # 把数据转进去多线程里面处理
    ser_send.start()

    time.sleep(600)

    pass

# END =================================================================

